package com.zx.zxpicture.shared.websocket;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.zx.zxpicture.shared.websocket.model.PictureEditResponseMessage;
import com.zx.zxpicture.shared.websocket.disruptor.PictureEditEventProducer;
import com.zx.zxpicture.shared.websocket.enums.PictureEditActionEnum;
import com.zx.zxpicture.shared.websocket.enums.PictureEditMessageTypeEnum;
import com.zx.zxpicture.shared.websocket.model.PictureEditRequestMessage;
import com.zx.zxpicture.domain.user.entity.User;
import com.zx.zxpicture.application.service.UserApplicationService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import javax.annotation.Resource;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 这才是用来接收消息和转发消息的
 * WebSocket处理器
 */
@Component
@Slf4j
public class PictureEditHandler extends TextWebSocketHandler {

    // 每张图片的编辑状态，key: pictureId, value: 当前正在编辑的用户 ID
    private final Map<Long, Long> pictureEditingUsers = new ConcurrentHashMap<>();

    // 保存所有连接的会话，key: pictureId, value: 用户会话集合(WebSocket通道集合)
    private final Map<Long, Set<WebSocketSession>> pictureSessions = new ConcurrentHashMap<>();
    @Resource
    private UserApplicationService userApplicationService;
    @Resource
    private PictureEditEventProducer pictureEditEventProducer;

    /**
     * 处理客户端发送的消息
     * @param session 客户端传来消息的会话
     * @param message 客户端传来的消息
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        //解析为Java对象
        PictureEditRequestMessage pictureEditRequestMessage = JSONUtil.toBean(message.getPayload(), PictureEditRequestMessage.class);
        //拿到操作的类型
        String type = pictureEditRequestMessage.getType();
        Map<String, Object> attributes = session.getAttributes();
        User user = (User) attributes.get("user");
        Long pictureId = (Long)attributes.get("pictureId");
        //发送消息
        pictureEditEventProducer.publishEvent(pictureEditRequestMessage, session, user, pictureId);
    }

    /**
     * 连接建立后逻辑
     * @param session ws会话
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        //用户建立连接，肯定刚开始编辑
        //将会话保存到集合中
        Long pictureId = (Long) session.getAttributes().get("pictureId");
        pictureSessions.putIfAbsent(pictureId, ConcurrentHashMap.newKeySet());
        pictureSessions.get(pictureId).add(session);
        Long editUserId = pictureEditingUsers.get(pictureId);
        User edituser = null;
        if(editUserId != null){
            edituser = userApplicationService.getById(editUserId);
        }

        //构造响应
        User user = (User) session.getAttributes().get("user");
        String message = String.format("用户 %s进入编辑",user.getUserName());
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
        //正在编辑图片的用户信息
        pictureEditResponseMessage.setUser(userApplicationService.getUserVO(edituser));
        pictureEditResponseMessage.setMessage(message);
        //传播对每一个用户
        broadcastToPicture(pictureId,pictureEditResponseMessage);
    }

    /**
     * 关闭连接后逻辑
     * @param session 断开连接的会话
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        Map<String, Object> attributes = session.getAttributes();
        Long pictureId = (Long) attributes.get("pictureId");
        User user = (User) attributes.get("user");
        // 移除当前用户的编辑状态
        handleExitEditMessage(null, session, user, pictureId);

        // 删除会话
        Set<WebSocketSession> sessionSet = pictureSessions.get(pictureId);
        if (sessionSet != null) {
            sessionSet.remove(session);
            if (sessionSet.isEmpty()) {
                pictureSessions.remove(pictureId);
            }
        }
        Long editUserId = pictureEditingUsers.get(pictureId);
        User edituser = null;
        if(editUserId != null){
            edituser = userApplicationService.getById(editUserId);
        }

        // 响应
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.INFO.getValue());
        String message = String.format("用户 %s离开编辑", user.getUserName());
        pictureEditResponseMessage.setMessage(message);
        //当前在编辑的用户
        pictureEditResponseMessage.setUser(userApplicationService.getUserVO(edituser));
        broadcastToPicture(pictureId, pictureEditResponseMessage);
    }

    /**
     * 用户进入编辑状态
     * @param pictureEditRequestMessage
     * @param session
     * @param user 正在编辑用户
     * @param pictureId
     */
    public void handleEnterEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception {
        //查看是否有用户正在编辑
        Long userId = user.getId();
        //返回null表示没有这个key
        Long lock = pictureEditingUsers.putIfAbsent(pictureId, userId);
        if(lock != null){
            log.error("当前图片有用户正在编辑");
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.ERROR.getValue());
            String message = "当前图片有用户正在编辑";
            pictureEditResponseMessage.setMessage(message);
            TextMessage textMessage = new TextMessage(JSONUtil.toJsonStr(pictureEditResponseMessage));
            session.sendMessage(textMessage);
            return;
        }
        //没有用户在编辑团队空间中的这个图片
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.ENTER_EDIT.getValue());
        String message = String.format("用户 %s开始编辑图片", user.getUserName());
        pictureEditResponseMessage.setMessage(message);
        //这个user就是正在编辑图片的用户
        pictureEditResponseMessage.setUser(userApplicationService.getUserVO(user));
        broadcastToPicture(pictureId, pictureEditResponseMessage);
    }

    /**
     * 用户执行各种编辑操作
     * 将信息发送给除了当前用户的其他用户
     * @param pictureEditRequestMessage
     * @param session
     * @param user 正在编辑用户
     * @param pictureId
     */
    public void handleEditActionMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception {
        Long editingUserId = pictureEditingUsers.get(pictureId);
        String editAction = pictureEditRequestMessage.getEditAction();
        PictureEditActionEnum actionEnum = PictureEditActionEnum.getEnumByValue(editAction);
        if (actionEnum == null) {
            return;
        }
        // 确认是当前编辑者
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EDIT_ACTION.getValue());
            String message = String.format("用户 %s执行%s操作", user.getUserName(), actionEnum.getValue());
            pictureEditResponseMessage.setMessage(message);
            pictureEditResponseMessage.setEditAction(editAction);
            //user是当前编辑用户
            pictureEditResponseMessage.setUser(userApplicationService.getUserVO(user));
            // 广播给除了当前客户端之外的其他用户，否则会造成重复编辑
            broadcastToPicture(pictureId, pictureEditResponseMessage, session);
        }
    }


    /**
     * 用户退出编辑状态
     * @param pictureEditRequestMessage
     * @param session
     * @param user 退出用户信息
     * @param pictureId
     */
    public void handleExitEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception {
        //判断当前用户是否是图片编辑者
        //只有当前图片编辑者才能退出编辑
        Long editingUserId = pictureEditingUsers.get(pictureId);
        if (editingUserId != null && editingUserId.equals(user.getId())) {
            // 移除当前用户的编辑状态
            pictureEditingUsers.remove(pictureId);
            // 构造响应，发送退出编辑的消息通知
            PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
            pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EXIT_EDIT.getValue());
            String message = String.format("用户 %s退出编辑图片", user.getUserName());
            pictureEditResponseMessage.setMessage(message);
            //退出用户的信息
            pictureEditResponseMessage.setUser(userApplicationService.getUserVO(user));
            broadcastToPicture(pictureId, pictureEditResponseMessage);
        }
    }

    /**
     * 用户完成编辑
     * @param pictureEditRequestMessage
     * @param session
     * @param user 完成编辑的用户
     * @param pictureId
     */
    public void handleCompleteEditMessage(PictureEditRequestMessage pictureEditRequestMessage, WebSocketSession session, User user, Long pictureId) throws Exception {
        //同步给共享编辑这张图片的除了自己的所有用户
        // 构造响应，发送退出编辑的消息通知
        PictureEditResponseMessage pictureEditResponseMessage = new PictureEditResponseMessage();
        pictureEditResponseMessage.setType(PictureEditMessageTypeEnum.EDIT_COMPLETE.getValue());
        String message = String.format("用户 %s完成了编辑图片", user.getUserName());
        pictureEditResponseMessage.setMessage(message);
        pictureEditResponseMessage.setUser(userApplicationService.getUserVO(user));
        broadcastToPicture(pictureId,pictureEditResponseMessage,session);
    }

    /**
     * 特定传播：将消息推送给正在编辑这个图片的所有用户
     * @param pictureId 图片id
     * @param pictureEditResponseMessage 团队空间图片编辑响应
     * @param excludeSession 排除的会话
     * @throws Exception
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage, WebSocketSession excludeSession) throws Exception {
        Set<WebSocketSession> webSocketSessions = pictureSessions.get(pictureId);
        if(CollUtil.isNotEmpty(webSocketSessions)){
            //自定义序列化器，将实体类转换为JSON字符串
            //为什么要自定义序列化器，因为pictureEditResponseMessage中有一个参数是Long类型的
            //如果不将这个Long转换为String类型，那么前端接收到的参数会将Long类型转换为Number(Number最大长度匹配不上Long)会精度丢失
            //创建ObjectMapper
            ObjectMapper objectMapper = new ObjectMapper();
            SimpleModule module = new SimpleModule();
            module.addSerializer(Long.class, ToStringSerializer.instance);
            module.addSerializer(Long.TYPE, ToStringSerializer.instance);
            objectMapper.registerModule(module);
            //序列化为JSON字符串
            String message = objectMapper.writeValueAsString(pictureEditResponseMessage);
            TextMessage textMessage = new TextMessage(message);
            //得到每一个正在编辑这个图片的用户会话
            for (WebSocketSession wsSession : webSocketSessions) {
                if(ObjUtil.isEmpty(wsSession) || wsSession == excludeSession){
                    continue;
                }
                if(wsSession.isOpen()){
                    wsSession.sendMessage(textMessage);
                }
            }
        }
    }

    /**
     * 全部广播：将消息推送给正在编辑这个图片的所有用户
     * @param pictureId
     * @param pictureEditResponseMessage
     * @throws Exception
     */
    private void broadcastToPicture(Long pictureId, PictureEditResponseMessage pictureEditResponseMessage) throws Exception {
        broadcastToPicture(pictureId, pictureEditResponseMessage, null);
    }
}
